/* This file is part of FireEscape.
 *
 * FireEscape is the intellectual work of Phillip Cheng & Allen Park.
 * All future releases or modifications of this program must include this
 * attribuation as according to the Creative Commons BY-SA license.
 *
 * FireEscape is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * FireEscape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FireEscape.  If not, see <http://www.gnu.org/licenses/>.
 *
 * All modifications to this program should include this original statement.
 *
 * If you modify, distribute, or use the program , please contact
 * fireescape@kloudvine.com
 * http://code.google.com/p/fireescape/
 *
 * If you are interested in using FireEscape for your structure, please contact us
 * for support.
 *
 * If you do use FireEscape, please consider donating to the project.
 */
package fireescape;

import java.awt.Color;
import java.util.ArrayList;
import java.awt.Graphics2D;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Scanner;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.awt.geom.Point2D;
import java.io.File;

/**
 * Represents the map
 * @author chengP
 */
public class Map {

    private static double xSize;
    private static double ySize;
    public static final Room outside = new Room();
    private static ArrayList<Floor> floors;
    private static ArrayList<Exit> exits;
    private static ArrayList<Integer> staircases;
    private static int numRooms;
    public static final boolean enableWindowJumping = true;// turn on to enable them to jump out the window
    public static final boolean testingNewMap = false;
    public static final boolean drawExitRects = true;
    //private static ArrayList<Room> botInjectableRooms;
//TODO death count

    /**
     * Constructs the map
     * @param xSiz The new x width
     * @param ySiz The new y length
     */
    public Map(double xSiz, double ySiz) {
        xSize = xSiz;
        ySize = ySiz;
        numRooms = 1;
        floors = new ArrayList<Floor>();
        exits = new ArrayList<Exit>();
        staircases = new ArrayList<Integer>();
        generateCloseRoutes();
    }

    /**
     * Resets the map
     */
    public static void reset() {
        for (Floor f : floors) {
            f.reset();
        }
        outside.reset();
    }

    /**
     * Initializes the map
     */
    public static void init() {
        /*testMap1();
        /**        testMap1();
        testMap2();
        testMap3();
        staircases();*/
        getMapFromServer();
        //testMap6();
        if (testingNewMap) {
            checkExits();
            checkRooms();
        }
    }

    /*public static void setUpMap(Image[] floorImages) {
    for (int i = 0; i < floors.size(); i++) {
    floors.get(i).paintRoomsOnly(floorImages[i]);
    }
    }*/
    /**
     * Checks to see if the exits are all linked
     */
    public static void checkExits() {
        for (Exit exit : exits) {
            if (!(exit instanceof Staircase)) {
                if (exit.getRoom1ID() == -1 && exit.getRoom2ID() == -1) {
                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, "Exit was not matched at all: " + exit);
                } else if (exit.getRoom1ID() == -1 || exit.getRoom2ID() == -1) {
                    //Logger.getLogger("FireEscapeLog").log(Level.FINE, "Exit was only matched to one: " + exit);
                    //System.out.println("Map");
                }
            }
        }
    }

    /**
     * Checks to see if there are no rooms that contain Bots but have no Exits
     */
    public static void checkRooms() {
        for (Floor floor : floors) {
            for (int j = 0; j < floor.getNumRooms(); j++) {
                Room room = floor.getRoom(j);
                if (room.injectable() && room.getRoomID() != 0 && room.getNumExits() == 0) {
                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, "Injectable room has no exits: " + room);
                }
            }
        }
    }

    /**
     * Gets the map from the server
     */
    public static void getMapFromServer() {
        //TODO UNCOMMENT AND COMMENT OUT TEMP CODE
        ArrayList<String> rawMapData = new ArrayList<String>();
        try {
            // get  server address
            /*URL kloudvine = new URL("http://www.kloudvine.com/Map.mp");
            URLConnection kloudvineConnect = kloudvine.openConnection();
            Scanner kIn = new Scanner(new InputStreamReader(kloudvineConnect.getInputStream()));*/
            Scanner kIn = new Scanner(new File("C:\\Map.mp"));
            while (kIn.hasNextLine()) {
                rawMapData.add(kIn.nextLine().replaceAll(" ", ""));
            }
            kIn.close();
       /* } catch (MalformedURLException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
*/
        } catch (IOException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

        } finally {
        }
        //Process map data
        //Map data input schema R/E/S|parameters
        for (String s : rawMapData) {
            // System.out.println(s);
            String[] splitMapData = s.split(";");
            if (splitMapData[0].equalsIgnoreCase("R")) {
                //Because this line is really freaking long
                Boolean e = Boolean.parseBoolean(splitMapData[1]);
                int f = Integer.parseInt(splitMapData[2]);
                ArrayList<Point2D.Double> bounds = new ArrayList<Point2D.Double>();
                for (int k = 3; k < splitMapData.length; k += 2) {
                    bounds.add(new Point2D.Double(Double.parseDouble(splitMapData[k]),
                            Double.parseDouble(splitMapData[k + 1])));
                    //System.out.println(splitMapData[k] + " " + splitMapData[k + 1]);
                }
                addRoom(bounds, e, f);
                bounds = null;
            } else if (splitMapData[0].equalsIgnoreCase("E")) {
                //Because this line is really freaking long
                double a = Double.parseDouble(splitMapData[2]);
                double b = Double.parseDouble(splitMapData[3]);
                double c = Double.parseDouble(splitMapData[4]);
                double d = Double.parseDouble(splitMapData[5]);
                int e = Integer.parseInt(splitMapData[1]);
                addExit(a, b, c, d, e);
            } else if (splitMapData[0].equalsIgnoreCase("S")) {
                //Because this line is really freaking long
                double a = Double.parseDouble(splitMapData[1]);
                double b = Double.parseDouble(splitMapData[2]);
                double c = Double.parseDouble(splitMapData[3]);
                double d = Double.parseDouble(splitMapData[4]);
                int e = Integer.parseInt(splitMapData[5]);
                int f = Integer.parseInt(splitMapData[6]);
                addStaircase(a, b, c, d, e, f);
            } else if (splitMapData[0].equalsIgnoreCase("F")) {
                double a = Double.parseDouble(splitMapData[1]);
                double b = Double.parseDouble(splitMapData[2]);
                floors.add(new Floor(a, b));

            }
//otherwise regard as comment
        }
    }

    /*public static void testMap1() {  //900 x 600
    int numFloor = 0;
    floors.add(new Floor(900, 600));
    addRoom(0, 0, 200, 100, true, numFloor);        //0
    addRoom(300, 0, 200, 200, true, numFloor);      //1
    addRoom(0, 200, 200, 200, true, numFloor);      //2
    addRoom(250, 400, 250, 100, true, numFloor);    //3
    addRoom(0, 100, 200, 100, false, numFloor);     //4
    addRoom(200, 0, 100, 200, false, numFloor);     //5
    addRoom(200, 200, 300, 200, false, numFloor);   //6
    addRoom(0, 400, 250, 100, false, numFloor);     //7
    addRoom(0, 500, 900, 100, false, numFloor);     //8
    addRoom(500, 0, 400, 500, false, numFloor);     //9
    addExit(150, 100, 175, 100, numFloor);
    addExit(200, 100, 200, 200, numFloor);
    addExit(200, 200, 300, 200, numFloor);
    addExit(200, 225, 200, 250, numFloor);
    addExit(325, 200, 350, 200, numFloor);
    addExit(200, 400, 250, 400, numFloor);
    addExit(250, 450, 250, 475, numFloor);
    addExit(300, 50, 300, 100, numFloor);
    addExit(0, 100, 0, 200, numFloor);
    addExit(200, 0, 300, 0, numFloor);
    addExit(500, 200, 500, 400, numFloor);
    addExit(0, 400, 0, 500, numFloor);
    addExit(0, 500, 0, 600, numFloor);
    addExit(0, 500, 250, 500, numFloor);
    addExit(0, 600, 900, 600, numFloor);
    addExit(900, 600, 900, 500, numFloor);
    addExit(900, 500, 900, 0, numFloor);
    addExit(500, 500, 900, 500, numFloor);
    addExit(500, 0, 900, 0, numFloor);
    }

    public static void testMap2() {  //500 x 500 |||  quasi 900 x 600
    int numFloor = 1;
    floors.add(new Floor(900, 600));
    addRoom(0, 0, 225, 100, true, numFloor);        //10
    addRoom(0, 100, 225, 100, true, numFloor);      //11
    addRoom(0, 200, 225, 100, true, numFloor);      //12
    addRoom(0, 300, 225, 100, true, numFloor);      //13
    addRoom(0, 400, 225, 100, true, numFloor);      //14
    addRoom(0, 500, 225, 100, true, numFloor);      //15
    addRoom(275, 0, 225, 100, true, numFloor);      //16
    addRoom(275, 100, 225, 100, true, numFloor);    //17
    addRoom(275, 200, 225, 100, true, numFloor);    //18
    addRoom(275, 400, 225, 100, true, numFloor);    //19
    addRoom(275, 500, 225, 100, true, numFloor);    //20
    addRoom(225, 0, 50, 300, false, numFloor);      //21
    addRoom(225, 300, 50, 100, false, numFloor);    //22
    addRoom(225, 400, 50, 200, false, numFloor);    //23
    addRoom(275, 300, 225, 100, false, numFloor);   //24
    addRoom(500, 500, 200, 100, false, numFloor);   //25
    addRoom(500, 0, 200, 500, false, numFloor);     //26
    addRoom(700, 0, 200, 600, false, numFloor);     //27

    //ENABLE WINDOWS--------------------------------------
    if (enableWindowJumping) {
    addExit(225, 0, 275, 0, numFloor);        //window
    addExit(225, 600, 275, 600, numFloor);
    }  //window <- when I get tired of programming - I turn this on
    //-----------------------------------------------------
    addExit(225, 300, 275, 300, numFloor);
    addExit(225, 400, 275, 400, numFloor);
    addExit(500, 325, 500, 375, numFloor);
    addExit(225, 50, 225, 75, numFloor);
    addExit(225, 150, 225, 175, numFloor);
    addExit(225, 250, 225, 275, numFloor);
    addExit(225, 350, 225, 375, numFloor);
    addExit(225, 450, 225, 475, numFloor);
    addExit(225, 525, 225, 550, numFloor);
    addExit(275, 50, 275, 75, numFloor);
    addExit(275, 150, 275, 175, numFloor);
    addExit(275, 250, 275, 275, numFloor);
    addExit(275, 300, 275, 400, numFloor);
    addExit(275, 450, 275, 475, numFloor);
    addExit(275, 525, 275, 550, numFloor);
    addExit(500, 525, 500, 575, numFloor);
    addExit(700, 525, 700, 575, numFloor);
    addExit(700, 200, 700, 250, numFloor);
    addExit(500, 125, 500, 175, numFloor);*/
    /*addExit(900, 50, 900, 150, numFloor);
    addExit(900, 250, 900, 350, numFloor);
    addExit(900, 450, 900, 550, numFloor);
    addExit(825, 0, 875, 0, numFloor);
    addExit(825, 600, 875, 600, numFloor);*/
    //}

    /*public static void testMap3() { //900 x 600
    int numFloor = 2;
    floors.add(new Floor(900, 600));
    addRoom(0, 0, 500, 300, true, numFloor);        //28
    addRoom(600, 0, 300, 100, true, numFloor);      //29
    addRoom(0, 400, 500, 200, true, numFloor);      //30
    addRoom(600, 300, 100, 300, true, numFloor);    //31
    addRoom(700, 300, 200, 300, true, numFloor);    //32
    addRoom(525, 250, 50, 100, true, numFloor);     //33
    addRoom(0, 300, 500, 100, false, numFloor);     //34
    addRoom(500, 0, 100, 250, false, numFloor);     //35
    addRoom(600, 100, 300, 200, false, numFloor);   //36
    addRoom(500, 350, 100, 250, false, numFloor);   //37
    addRoom(500, 250, 25, 100, false, numFloor);    //38
    addRoom(575, 250, 25, 100, false, numFloor);    //39
    addExit(100, 300, 200, 300, numFloor);
    addExit(100, 400, 200, 400, numFloor);
    addExit(500, 100, 500, 200, numFloor);
    addExit(500, 450, 500, 550, numFloor);
    addExit(525, 300, 525, 325, numFloor);
    addExit(535, 250, 565, 250, numFloor);
    addExit(600, 25, 600, 75, numFloor);
    addExit(600, 400, 600, 500, numFloor);
    addExit(625, 300, 675, 300, numFloor);
    addExit(700, 400, 700, 500, numFloor);
    addExit(750, 300, 850, 300, numFloor);

    addExit(500, 300, 500, 350, numFloor);
    addExit(500, 350, 500, 400, numFloor);
    addExit(500, 250, 525, 250, numFloor);
    addExit(500, 350, 525, 350, numFloor);
    addExit(575, 250, 600, 250, numFloor);
    addExit(575, 350, 600, 350, numFloor);
    addExit(600, 100, 600, 250, numFloor);
    addExit(600, 250, 600, 300, numFloor);
    }*/
    //when adding new map, comment break in Floor.addExit(double, double, double, double) to check for errors
    /*public static void staircases() {
    addStaircase(725, 50, 775, 50, 0, 2);
    addStaircase(25, 25, 25, 75, 0, 1);
    addStaircase(25, 525, 25, 575, 0, 1);
    addStaircase(225, 25, 275, 25, 1, 2);
    addStaircase(225, 575, 275, 575, 1, 2);
    }*/
    public static void testMap4() {
        //ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
        int numFloor = 0;
        floors.add(new Floor(900, 600));
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 6; j++) {
                ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
                double xCenter = 50 + 100 * i;
                double yCenter = 50 + 100 * j;
                points.add(new Point2D.Double(xCenter + 50, yCenter - 25));
                points.add(new Point2D.Double(xCenter + 50, yCenter + 25));
                points.add(new Point2D.Double(xCenter + 25, yCenter + 50));
                points.add(new Point2D.Double(xCenter - 25, yCenter + 50));
                points.add(new Point2D.Double(xCenter - 50, yCenter + 25));
                points.add(new Point2D.Double(xCenter - 50, yCenter - 25));
                points.add(new Point2D.Double(xCenter - 25, yCenter - 50));
                points.add(new Point2D.Double(xCenter + 25, yCenter - 50));
                Map.addRoom(points, true, numFloor);
            }
        }
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 6; j++) {
                double xCenter = 50 + 100 * i;
                double yCenter = 50 + 100 * j;
                Map.addExit(xCenter + 50, yCenter - 10, xCenter + 50, yCenter + 10, numFloor);
                Map.addExit(xCenter - 10, yCenter + 50, xCenter + 10, yCenter + 50, numFloor);
            }
        }
        for (int i = 0; i < 9; i++) {
            double xCenter = 50 + 100 * i;
            Map.addExit(xCenter - 10, 0, xCenter + 10, 0, numFloor);
        }
        for (int j = 0; j < 6; j++) {
            double yCenter = 50 + 100 * j;
            Map.addExit(0, yCenter - 10, 0, yCenter + 10, numFloor);
        }
    }

    public static void testMap5() {
        //ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
        int numFloor = 0;
        floors.add(new Floor(900, 600));
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 6; j++) {
                ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
                double xCenter = 50 + 100 * i;
                double yCenter = 50 + 100 * j;
                points.add(new Point2D.Double(xCenter + 50, yCenter - 25));
                points.add(new Point2D.Double(xCenter + 50, yCenter + 25));
                points.add(new Point2D.Double(xCenter + 25, yCenter + 50));
                points.add(new Point2D.Double(xCenter - 25, yCenter + 50));
                points.add(new Point2D.Double(xCenter - 50, yCenter + 25));
                points.add(new Point2D.Double(xCenter - 50, yCenter - 25));
                points.add(new Point2D.Double(xCenter - 25, yCenter - 50));
                points.add(new Point2D.Double(xCenter + 25, yCenter - 50));
                Map.addRoom(points, true, numFloor);
            }
        }
        for (int i = 1; i <= 8; i++) {
            for (int j = 1; j <= 5; j++) {
                ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
                double xCenter = 100 * i;
                double yCenter = 100 * j;
                points.add(new Point2D.Double(xCenter + 25, yCenter));
                points.add(new Point2D.Double(xCenter, yCenter + 25));
                points.add(new Point2D.Double(xCenter - 25, yCenter));
                points.add(new Point2D.Double(xCenter, yCenter - 25));
                Map.addRoom(points, true, numFloor);
            }
        }
        for (int i = 1; i <= 8; i++) {
            for (int j = 1; j <= 5; j++) {
                double xCenter = 100 * i;
                double yCenter = 100 * j;
                Map.addExit(xCenter + 5, yCenter + 20, xCenter + 20, yCenter + 5, numFloor);
                Map.addExit(xCenter + 5, yCenter - 20, xCenter + 20, yCenter - 5, numFloor);
                Map.addExit(xCenter - 5, yCenter + 20, xCenter - 20, yCenter + 5, numFloor);
                Map.addExit(xCenter - 5, yCenter - 20, xCenter - 20, yCenter - 5, numFloor);
            }
        }
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 6; j++) {
                double xCenter = 50 + 100 * i;
                double yCenter = 50 + 100 * j;
                Map.addExit(xCenter + 50, yCenter - 10, xCenter + 50, yCenter + 10, numFloor);
                Map.addExit(xCenter - 10, yCenter + 50, xCenter + 10, yCenter + 50, numFloor);
            }
        }
        for (int i = 0; i < 9; i++) {
            double xCenter = 50 + 100 * i;
            Map.addExit(xCenter - 10, 0, xCenter + 10, 0, numFloor);
        }
        for (int j = 0; j < 6; j++) {
            double yCenter = 50 + 100 * j;
            Map.addExit(0, yCenter - 10, 0, yCenter + 10, numFloor);
        }
    }

    public static void testMap6() {
        //ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
        int numFloor = 0;
        floors.add(new Floor(900, 600));
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 6; j++) {
                ArrayList<Point2D.Double> points = new ArrayList<Point2D.Double>();
                double xCenter = 50 + 100 * i;
                double yCenter = 50 + 100 * j;
                points.add(new Point2D.Double(xCenter + 50, yCenter - 25));
                points.add(new Point2D.Double(xCenter + 50, yCenter + 25));
                points.add(new Point2D.Double(xCenter + 25, yCenter + 50));
                points.add(new Point2D.Double(xCenter - 25, yCenter + 50));
                points.add(new Point2D.Double(xCenter - 50, yCenter + 25));
                points.add(new Point2D.Double(xCenter - 50, yCenter - 25));
                points.add(new Point2D.Double(xCenter - 25, yCenter - 50));
                points.add(new Point2D.Double(xCenter + 25, yCenter - 50));
                Map.addRoom(points, true, numFloor);
            }
        }
        for (int i = 1; i <= 8; i++) {
            for (int j = 1; j <= 5; j++) {
                double xCenter = 100 * i;
                double yCenter = 100 * j;
                Map.addExit(xCenter + 5, yCenter + 20, xCenter + 20, yCenter + 5, numFloor);
                Map.addExit(xCenter + 5, yCenter - 20, xCenter + 20, yCenter - 5, numFloor);
                Map.addExit(xCenter - 5, yCenter + 20, xCenter - 20, yCenter + 5, numFloor);
                Map.addExit(xCenter - 5, yCenter - 20, xCenter - 20, yCenter - 5, numFloor);
            }
        }
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 5; j++) {
                double xCenter = 50 + 100 * i;
                double yCenter = 50 + 100 * j;
                Map.addExit(xCenter + 50, yCenter - 10, xCenter + 50, yCenter + 10, numFloor);
                Map.addExit(xCenter - 10, yCenter + 50, xCenter + 10, yCenter + 50, numFloor);
            }
        }
    }

    /**
     * Adds a room
     * @param x1 The x coordinate of the first corner
     * @param y1 The y coordinate of the first corner
     * @param x2 The x coordinate of the second (opposite) corner
     * @param y2 The y coordinate of the second (opposite) corner
     * @param botInjectable Whether the room can start with bots
     * @param floorNum The floor number of the room
     */
    public static void addRoom(ArrayList<Point2D.Double> points, boolean botInjectable, int floorNum) {
        floors.get(floorNum).addRoom(points, numRooms, floorNum, botInjectable);
        numRooms++;
        if (botInjectable) {
            GeneticAI.setNumRoutes(GeneticAI.getNumRoutes() + 1);
        }
    }

    /**
     * Adds an exit
     * @param x1 The x coordinate of the first corner
     * @param y1 The y coordinate of the first corner
     * @param x2 The x coordinate of the second corner
     * @param y2 The y coordinate of the second corner
     * @param floorNum The floor number of the new exit
     */
    public static void addExit(double x1, double y1, double x2, double y2, int floorNum) {
        Exit newExit = new Exit(x1, y1, x2, y2, floorNum, exits.size());
        addExit(newExit, floorNum);
        if (Map.testingNewMap && newExit.getLength() < 2 * BotAI.botRadius) {
            Logger.getLogger("FireEscapeLog").log(Level.FINE, "Exit is not long enough");
            //System.out.println("Map");
        }
    }

    /**
     * Adds an exit
     * @param exit The new exit
     * @param floorNum The floor number of the new exit
     */
    public static void addExit(Exit exit, int floorNum) {
        floors.get(floorNum).addExit(exit, floorNum);
        exits.add(exit);
        //whatever is changed in here should probably also be added in addStaircase
    }

    /**
     * Returns an exit
     * @param exitNum The exit ID
     * @return The exit
     */
    public static Exit getExit(int exitNum) {
        return exits.get(exitNum);
    }

    /**
     * Returns the number of exits
     * @return The number of exits
     */
    public static int getNumExits() {
        return exits.size();
    }

    /**
     * Adds a new staircase
     * @param x1 The x coordinate of the first corner
     * @param y1 The y coordinate of the first corner
     * @param x2 The x coordinate of the second corner
     * @param y2 The y coordinate of the second corner
     * @param startFloor The bottom floor of the staircase
     * @param endFloor The top floor of the staircase
     */
    public static void addStaircase(double x1, double y1, double x2, double y2, int startFloor, int endFloor) {
        Room bottomRoom = whichRoom(x1, y1, x2, y2, startFloor);
        Staircase staircase = new Staircase(x1, y1, x2, y2, bottomRoom.getRoomID(), startFloor, endFloor, getNumExits(), getNumStaircases());
        if (Map.testingNewMap && staircase.getLength() < 2 * BotAI.botRadius) {
            Logger.getLogger("FireEscapeLog").log(Level.FINE, "Staircase is not long enough");
            // System.out.println("Map");
        }
        bottomRoom.addExit(staircase);
        exits.add(staircase);
        //floors.get(startFloor).addToExits(staircase);
        for (int i = startFloor + 1; i <= endFloor; i++) {
            whichRoom(x1, y1, x2, y2, i).addExit(staircase);
            //floors.get(i).addToExits(staircase);
        }
        staircases.add(exits.size() - 1);
    }

    public static Staircase getStaircase(int stairIndex) {
        return (Staircase) exits.get(staircases.get(stairIndex));
    }

    public static int getNumStaircases() {
        return staircases.size();
    }

    /**
     * Returns which room the coordinates are in
     * @param x1 The x coordinate of the first coordinates
     * @param y1 The y coordinate of the first coordinates
     * @param x2 The x coordinate of the second coordinates
     * @param y2 The y coordinate of the second coordinates
     * @param floorNum The floor number the coordinates are in
     * @return The room the coordinates are in
     */
    public static Room whichRoom(double x1, double y1, double x2, double y2, int floorNum) {
        return floors.get(floorNum).whichRoom(x1, y1, x2, y2);
    }

    /**
     * Generates the routes
     */
    public static void generateRoutes() {
        ArrayList<Route> routes = new ArrayList<Route>();
        for (Floor f : floors) {
            //System.out.println("got here1");
            //for (int i = 0; i < botInjectableRooms.size(); i++) {
            for (int i = 0; i < f.getNumRooms(); i++) {
                //System.out.println("got here2");
                Room room = f.getRoom(i);
                if (room.injectable()) {
                    //System.out.println("got here3");
                    Route route = new Route();
                    ArrayList<Room> rooms = new ArrayList<Room>();             //only used to eliminate looping
                    //int roomID = botInjectableRooms.get(i);
                    //Room room = rooms.get(roomID);
                    Exit oldExit = null;
                    route.setFirstRoom(room.getRoomID());
                    rooms.add(room);
                    while (room != outside) {
                        //System.out.println("got here4");
                        Exit exit = room.randomExit();
                        /*int outsideIndex = room.hasDoorToOutside();
                        if (outsideIndex == -1) {
                        exit = room.randomExit();
                        } else {
                        exit = room.getExit(outsideIndex);
                        }*/
                        Room temp = getRoom(exit.connectedToID(room.getRoomID()));
                        /*sigh while ((temp.getNumExits() == 1 && temp.getRoomID() != 0) || exit == oldExit) {
                        System.out.println("got here5");
                        exit = room.randomExit();
                        temp = getRoom(exit.connectedToID(room.getRoomID()));
                        }*/
                        room = temp;
                        rooms.add(room);
                        oldExit = exit;
                        route.addNavPoint(exit);
                    }
                    if (route.getRouteSize() == 0) {
                        //System.out.println("got here6");
                        Logger.getLogger("FireEscapeLog").log(Level.SEVERE, "Route was not generated");
                    }
                    route = filterLoops(route, rooms);
                    routes.add(route);
                    //System.out.println("got here7");
                }
            }
        }
        ServerAI.setRoutes(routes);
        GeneticAI.setRoutes(routes);
    }

    /**
     * Generates the routes from each room that go to the closest exit
     */
    public static Genome generateCloseRoutes() {
        for (Exit exit : exits) {
            if (exit.connectedToOutside()) {
                ArrayList<Integer> routeSoFar = new ArrayList<Integer>();
                routeSoFar.add(exit.getExitID());
                generateCloseRoute(exit, Map.getRoom(exit.connectedToID(0)), routeSoFar, 0);
            }
        }
        List<Route> routes = new ArrayList<Route>();
        for (int i = 0; i < floors.size(); i++) {
            Floor floor = floors.get(i);
            for (int j = 0; j < floor.getNumRooms(); j++) {
                Room room = floor.getRoom(j);
                if (!room.unreachable()) {
                    Route route = new Route();
                    route.setFirstRoom(room.getRoomID());
                    room.addClosestRouteToRoute(route);
                    routes.add(route);
                }
            }
        }
         return new Genome(0, routes, 0, false, 0);

    }

    public static void generateCloseRoute(Exit exit, Room room, ArrayList<Integer> routeSoFar, double distance) {
        for (int i = 0; i < room.getNumExits(); i++) {
            Exit exit2 = room.getExit(i);
            if (!exit2.equals(exit) && !exit2.connectedToOutside()) {
                Room nextRoom = Map.getRoom(exit.connectedToID(room.getRoomID()));
                double distance2 = distance + exit.centerDistance(exit2) + nextRoom.exitToCenter(exit2);
                if (distance < nextRoom.getClosestDistance()) {
                    nextRoom.setClosestDistance(distance);
                    routeSoFar.add(exit2.getExitID());
                    generateCloseRoute(exit2, nextRoom, routeSoFar, distance2 - nextRoom.exitToCenter(exit2));
                }
            }
        }
    }

    /**
     * Filters the loops from the route
     * @param route The route
     * @param rooms The rooms the route goes through
     * @return The new filtered route
     */
    public static Route filterLoops(Route route, ArrayList<Room> rooms) {
        for (int i = 0; i < route.getRouteSize(); i++) {
            Room room = rooms.get(i);
            for (int j = route.getRouteSize() - 1; j > i; j--) {
                if (room.equals(rooms.get(j))) {
                    route.removeSection(i, j);
                    rooms = removeSection(i, j, rooms);
                    j -= j - i;
                }
            }
        }
        return route;
    }

    /**
     * Removes a section from the ArrayList
     * @param start The starting index of the removal
     * @param end The ending index of the removal (this index is not removed)
     * @param rooms The ArrayList
     * @return The new ArrayList
     */
    public static ArrayList<Room> removeSection(int start, int end, ArrayList<Room> rooms) {
        ArrayList<Room> newRooms = new ArrayList<Room>();
        for (int i = 0; i < rooms.size(); i++) {
            if (i < start || i >= end) {
                newRooms.add(rooms.get(i));
            }
        }
        return newRooms;
    }

    /*public static void addBots(int numRoutes) {
    for (int i = 0; i < numRoutes; i++) {
    int firstRoomID = GeneticAI.getRoute(i).getFirstRoom();
    Room firstRoom = Map.getRoom(firstRoomID);
    for (int j = 0; j < 10; j++) {
    double newX = firstRoom.getX() + Math.random() * firstRoom.getWidth();
    double newY = firstRoom.getY() + Math.random() * firstRoom.getHeight();
    Bot bot = new Bot(i * 10 + j, i, firstRoomID, newX, newY);
    bot.setVelocity(5);
    bot.checkOnExit();
    BotAI.addBot(bot);
    }
    }
    }*/
    /**
     * Returns a room
     * @param roomID The room ID
     * @return The room
     */
    public static Room getRoom(int roomID) {
        if (roomID == 0) {
            return outside;
        }
        int numRoomsSoFar = 1;
        for (Floor f : floors) {
            int totalAfter = numRoomsSoFar + f.getNumRooms();
            if (roomID >= numRoomsSoFar && roomID < totalAfter) {
                return f.getRoom(roomID - numRoomsSoFar);
            }
            numRoomsSoFar = totalAfter;
        }
        return null;
    }

    /**
     * Returns a floor
     * @param num The floor number
     * @return The floor
     */
    public static Floor getFloor(int num) {
        return floors.get(num);
    }

    /**
     * Returns the number of floors
     * @return The number of floors
     */
    public static int getNumFloors() {
        return floors.size();
    }

    /**
     * Returns the number of room
     * @return The number of rooms
     */
    public static int getNumRooms() {
        return numRooms;
    }
    /*public static Room randomRoom() {
    Floor f = floors.get((int)(Math.random() * floors.size()));
    return f.getRoom((int) (Math.random() * f.getNumRooms()));
    }*/

    /**
     * Draws a floor
     * @param g The Graphics2D object used for drawing
     * @param floorNum The floor number to be drawn
     */
    public static void paint(Graphics2D g, int floorNum) {
        floors.get(floorNum).paint(g);
        //g.drawImage(floors.get(floorNum).paint(), 0, 0, null);
    }

    /*public static Image[] routeToImage(Route route, Image[] floorImages) {
    Image[] floorImages = new Image[floors.size()];
    Graphics2D[] floorGraphics = new Graphics2D[floors.size()];
    for (int i = 0; i < floors.size(); i++) {

    floorImages[i] = floors.get(i).paintRoomsOnly();//copyFloorImage();
    floorGraphics[i] = (Graphics2D) floorImages[i].getGraphics();
    }
    Room firstRoom = getRoom(route.getFirstRoom());
    Graphics2D g = floorGraphics[firstRoom.getFloorNum()];
    g.setColor(Color.green);
    g.fill(new Ellipse2D.Double(firstRoom.getCenterX() - 2, firstRoom.getCenterY() - 2, 4, 4));
    Point2D.Double pastPoint = new Point2D.Double(firstRoom.getCenterX(), firstRoom.getCenterY());
    for (int i = 0; i < route.getRouteSize(); i++) {
    Exit exit = route.getNavPoint(i);
    Point2D.Double currentPoint = new Point2D.Double(exit.getCenterX(), exit.getCenterY());
    g.draw(new Line2D.Double(pastPoint, currentPoint));
    if (exit instanceof Staircase) {
    g = floorGraphics[exit.connectedTo(null).getFloorNum()];
    }
    pastPoint = currentPoint;
    }
    return floorImages;
    }*/
}
